package sudoku;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.junit.Assert;

import algo.CheckValid;

public class SudokuSpielfeld {

	private final int FIELDLENGTH = 9;

	private SudokuEinzelfeld[][] sudokuFeld = new SudokuEinzelfeld[getFIELDLENGTH()][getFIELDLENGTH()];
	// int [][] sudokuFeldArray = new int [FIELDLENGTH][FIELDLENGTH];

	/**
	 * This map saves the possible numbers of every single field. The single
	 * fields are numbered like Every Index is:<br>
	 * row*10 + [1][2][3][4][5][6][7][8][9] <br>
	 * First row = 0*10+Index:<br>
	 * [ 1] [2] [3] [4] [5] [6] [7] [8] [9]<br>
	 * [11][12][13][14][15][16][17][18][19] ....
	 */
	private TreeMap<Integer, Set<Integer>> possibleNumbers = new TreeMap<Integer, Set<Integer>>();

	public SudokuSpielfeld(final String path) {
		initField();
		setBlockNr();

		try {
			sudokuEinlesen(path);
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	// public final Map<Integer, Set<Integer>> setPossibleNumbers() {
	// possibleNumbers = new TreeMap<Integer, Set<Integer>>();
	// ConvertStructToArray conv = new ConvertStructToArray(this);
	// for (int rows = 0; rows < getFIELDLENGTH(); rows++) {
	// for (int idx = 1; idx < getFIELDLENGTH() + 1; idx++) {
	// SudokuEinzelfeld field = returnEinzelnesFeld(rows, idx - 1);
	// if (field.getZahl() == 0) {
	// Set<Integer> possNrs = field.getHinterlegtesArray();
	// if (possNrs.size() == 1) {
	// field.setZahl((Integer) possNrs.toArray()[0]);
	// } else {
	// Set<Integer> newPossNrs = conv
	// .returnPossibleNumbersWithLookUpToOtherStructs(field);
	// if (!possNrs.isEmpty()) {
	// if (possNrs.size() < newPossNrs.size()) {
	// newPossNrs = possNrs;
	// }
	// }
	//
	// possibleNumbers.put((rows * 10) + idx, newPossNrs);
	// field.setPossNumbers(newPossNrs);
	//
	// }
	// } else {
	// possibleNumbers.put((rows * 10) + idx,
	// new HashSet<Integer>());
	// }
	//
	// }
	// }
	//
	// System.out.println("finished");
	// return possibleNumbers;
	// }

	public final int convert2dToID(int row, int col) {
		return (row * 10) + (col + 1);
	}

	public final int[] convertIdto2d(int id) {
		int[] coord = new int[2];
		coord[0] = id / 10;
		coord[1] = id % 10 - 1;
		return coord;
	}

	public SudokuSpielfeld(final SudokuEinzelfeld[][] lastField) {
		this.setSudokuFeld(lastField);
	}

	public SudokuSpielfeld(final boolean read) {

		initField();

		// Blocknummern setzen
		setBlockNr();

		if (read) {
			// Inhalte einfuegen
			String path = "src/main/resources/sudoku.txt";
			try {
				sudokuEinlesen(path);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public SudokuSpielfeld() {

		initField();

		SudokuEinzelfeld sf = getSudokuFeld()[0][0];
		Assert.assertEquals(9, sf.getPossibleNumbers().size());
		// Blocknummern setzen
		setBlockNr();
		Assert.assertEquals(9, sf.getPossibleNumbers().size());
		// Inhalte einfuegen
		String path = "src/main/resources/sudoku.txt";
		try {
			sudokuEinlesen(path);
		} catch (IOException e) {
			e.printStackTrace();
		}
		Assert.assertEquals(9, sf.getPossibleNumbers().size());

	}

	private void initField() {
		// Felder anlegen
		for (int zeile = 0; zeile < 9; zeile++) {
			for (int spalte = 0; spalte < 9; spalte++) {
				getSudokuFeld()[zeile][spalte] = new SudokuEinzelfeld(this,
						zeile, spalte, convert2dToID(zeile, spalte));
			}
		}

	}

	public final void sudokuEinlesen(String path) throws IOException {
		int zeilenCounter = 0;
		try {
			BufferedReader in = new BufferedReader(new FileReader(path));
			String line = null;
			while (zeilenCounter < 9) {
				while ((line = in.readLine()) != null) {

					for (int i = 0; i < 9; i++) {
						int number = (int) (line.charAt(i) - '0');
						getSudokuFeld()[zeilenCounter][i].setNumber(number);
						// sudokuFeldArray[zeilenCounter][i] = zahl;
					}
					zeilenCounter++;
				}
			}
		} catch (FileNotFoundException fnf) {
			fnf.printStackTrace();
			System.exit(1);
		}

	}

	public final void printSudoku() {
		System.out.println();
		for (int zeile = 0; zeile < 9; zeile++) {
			if (zeile % 3 == 0)
				printLine();
			for (int spalte = 0; spalte < 9; spalte++) {
				if (spalte % 3 == 0 && spalte > 0)
					System.out.print(" | ");
				if (getSudokuFeld()[zeile][spalte].getNumber() == 0) {
					System.out.print("[ ]");
				} else
					System.out.print("["
							+ getSudokuFeld()[zeile][spalte].getNumber() + "]");

			}
			System.out.println();

		}
		// System.out.println();
		printLine();

	}

	public final void printPossibleNumbers() {
		for (int i = 0; i < FIELDLENGTH; i++) {
			for (int j = 0; j < FIELDLENGTH; j++) {
				SudokuEinzelfeld sf = sudokuFeld[i][j];
				if (sf.getNumber() == 0) {
					Set<Integer> possNumbers = sf.getPossibleNumbers();
					if (possNumbers.isEmpty()) {
						System.err.print(sf.getId());
					}
					System.out.print(sf.getId() + ": "
							+ sf.getPossibleNumbers());
				}
			}
			System.out.println();
		}
	}

	private void printLine() {
		System.out.println("---------------------------------");
	}

	public final int returnZahl(int zeile, int spalte) {

		return getSudokuFeld()[zeile][spalte].getNumber();
	}

	public SudokuEinzelfeld returnSinglefield(int id) {
		int coord[] = convertIdto2d(id);
		return sudokuFeld[coord[0]][coord[1]];
	}

	public final SudokuEinzelfeld returnEinzelnesFeld(int zeile, int spalte) {
		return getSudokuFeld()[zeile][spalte];
	}

	public final void setBlockNr() {
		// System.out.println("---------" + this.getClass());
		int nr = 0;

		for (int zeile = 0; zeile < 9; zeile++) { // 0-2 = 1, 3-5=2 etc
			if (zeile < 3) {
				nr = 1;
			}

			else if (zeile >= 3 && zeile < 6) {
				nr = 4;
			} else {
				nr = 7;
			}
			for (int spalte = 0; spalte < 9; spalte++) {

				if (spalte % 3 == 0 && spalte != 0) {
					nr++;
				}

				// System.out.println("Zeile / Spalte " + zeile + "/" + spalte +
				// " Nr. " + nr);
				getSudokuFeld()[zeile][spalte].setBlockNr(nr);
			}
		}

	}

	public void setNumber(int id, int number) {
		int coord[] = convertIdto2d(id);
		sudokuFeld[coord[0]][coord[1]].setNumber(number);

		/* Remove from other constructs */
		removeFromConstructs(this, sudokuFeld[coord[0]][coord[1]]);

	}

	private void removeFromConstructs(SudokuSpielfeld sf, SudokuEinzelfeld sef) {
		List<SudokuEinzelfeld> row = sf.returnRow(sef.getRow());
		List<SudokuEinzelfeld> block = sf.returnblock(sef.getBlockNr());
		List<SudokuEinzelfeld> col = sf.returnCol(sef.getCol());
		for (int i = 0; i < row.size(); i++) {
			SudokuEinzelfeld sefTmp = row.get(i);
			sefTmp.removeNumberFromPossibleNumbers(sef.getNumber());
			sefTmp = col.get(i);
			sefTmp.removeNumberFromPossibleNumbers(sef.getNumber());
			sefTmp = block.get(i);
			sefTmp.removeNumberFromPossibleNumbers(sef.getNumber());
		}

	}

	public final void setNumberWithID(int id, int number, String algo) {
		System.out.println("Set number " + number + " at field  " + id
				+ " with Algo " + algo);
		setNumber(id, number);
	}

	public final List<Integer> returnblockInts(int blocknr) {
		List<Integer> ints = new ArrayList<Integer>();
		int j = blocknr;
		int zeilestart = 0;
		int spaltestart = 0;
		if (j == 1) {
			zeilestart = 0;
			spaltestart = 0;
		} else if (j == 2) {
			zeilestart = 0;
			spaltestart = 3;
		} else if (j == 3) {
			zeilestart = 0;
			spaltestart = 6;
		} else if (j == 4) {
			zeilestart = 3;
			spaltestart = 0;
		} else if (j == 5) {
			zeilestart = 3;
			spaltestart = 3;
		} else if (j == 6) {
			zeilestart = 3;
			spaltestart = 6;
		} else if (j == 7) {
			zeilestart = 6;
			spaltestart = 0;
		} else if (j == 8) {
			zeilestart = 6;
			spaltestart = 3;
		} else if (j == 9) {
			zeilestart = 6;
			spaltestart = 6;
		}
		for (int i = zeilestart; i < zeilestart + 3; i++) {
			for (int k = spaltestart; k < spaltestart + 3; k++) {
				int input = sudokuFeld[i][k].getNumber();
				if (input != 0) {
					ints.add(input);
				}
			}
		}
		return ints;

	}

	/**
	 * 
	 * @param row
	 *            starts with 0
	 * @return the complete row
	 */
	public final List<SudokuEinzelfeld> returnRow(int row) {
		List<SudokuEinzelfeld> rowList = new LinkedList<SudokuEinzelfeld>();
		for (int col = 0; col < 9; col++) {
			rowList.add(getSudokuFeld()[row][col]);
		}
		return rowList;
	}

	/**
	 * 
	 * @param col
	 *            starts with 0
	 * @return the complete column
	 */
	public final List<SudokuEinzelfeld> returnCol(int col) {
		List<SudokuEinzelfeld> colList = new LinkedList<SudokuEinzelfeld>();
		for (int row = 0; row < 9; row++) {
			colList.add(getSudokuFeld()[row][col]);
		}
		return colList;
	}

	public final List<SudokuEinzelfeld> returnblock(final int blocknr) {
		List<SudokuEinzelfeld> blockList = new LinkedList<SudokuEinzelfeld>();
		for (int zeile = 0; zeile < 9; zeile++) {
			for (int spalte = 0; spalte < 9; spalte++) {
				if (getSudokuFeld()[zeile][spalte].getBlockNr() == blocknr) {
					blockList.add(getSudokuFeld()[zeile][spalte]);
				}
			}
		}
		return blockList;
	}

	public final boolean isValid(SudokuEinzelfeld[][] sudokuFeld) {
		return validateCompleteSudoku();
	}

	public final boolean isReady(SudokuEinzelfeld[][] sudokuFeld) {
		for (int i = 0; i < sudokuFeld.length; i++) {
			for (int j = 0; j < sudokuFeld.length; j++) {
				if (sudokuFeld[i][j].getNumber() == 0) {
					return false;
				}
			}
		}
		return true;
	}

	public final List<Integer> returnzeileInts(int zeile) {
		List<Integer> zeileL = new ArrayList<Integer>();
		for (int i = 0; i < sudokuFeld.length; i++) {
			int input = sudokuFeld[zeile][i].getNumber();
			if (input != 0) {
				zeileL.add(input);
			}
		}
		return zeileL;
	}

	public final List<Integer> returnspalteInts(int spalte) {
		List<Integer> spalteL = new ArrayList<Integer>();
		for (int i = 0; i < sudokuFeld.length; i++) {
			int input = sudokuFeld[i][spalte].getNumber();
			if (input != 0) {
				spalteL.add(input);
			}
		}
		return spalteL;
	}

	public final void setField(int[][] validArray) throws Exception {
		if (validArray.length != getSudokuFeld().length) {
			throw new Exception("Wrong field size");
		}
		for (int i = 0; i < validArray.length; i++) {
			for (int j = 0; j < validArray.length; j++) {
				getSudokuFeld()[i][j].setNumber(validArray[i][j]);
			}
		}

	}

	public final boolean validateCompleteSudoku() {
		CheckValid validator = new CheckValid();
		boolean isValid = true;
		for (int i = 0; i < getFIELDLENGTH(); i++) {
			List<Integer> part = returnblockInts(i);
			isValid = validator.isValid(part);
			if (!isValid) {
				return isValid;
			}
			part = returnspalteInts(i);
			isValid = validator.isValid(part);
			if (!isValid) {
				return isValid;
			}
			part = returnzeileInts(i);
			isValid = validator.isValid(part);
			if (!isValid) {
				return isValid;
			}
		}
		return isValid;
	}

	private final void setSudokuFeld(SudokuEinzelfeld[][] sudokuFeld) {
		this.sudokuFeld = sudokuFeld;
	}

	public final SudokuEinzelfeld[][] getSudokuFeld() {
		return sudokuFeld;
	}

	public int getFIELDLENGTH() {
		return FIELDLENGTH;
	}

	public Map<Integer, Set<Integer>> getPossibleNumbers() {
		possibleNumbers = new TreeMap<Integer, Set<Integer>>();
		for (int i = 0; i < this.getSudokuFeld().length; i++) {
			for (int j = 0; j < this.getSudokuFeld()[i].length; j++) {
				possibleNumbers.put(this.getSudokuFeld()[i][j].getId(),
						this.getSudokuFeld()[i][j].getPossibleNumbers());
			}
		}
		return possibleNumbers;
	}

	/*
	 * Ueberlegeungen um ein Sudoku zu loesen. 1. Moeglichkeit: Alle Felder
	 * durchlaufen und die hinterlegten noch moeglichen Zahlen minimieren 1.1
	 * WAs tun, wenn es mehr als 1 Moeglichkeit gibt?
	 */
}
